package com.bub.pay.framework.common.util;

import com.bub.pay.framework.common.base.Cause;
import com.bub.pay.framework.common.exception.GlobalException;
import org.apache.commons.math3.distribution.EnumeratedDistribution;
import org.apache.commons.math3.util.Pair;

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;

public class RandomUtil {
    private static final String base = "abcdefghjkmnopqrstuvwxyz0123456789ABCDEFGHJKLMNOPQRSTUVWXYZ";

    private static final SecureRandom random;

    static {
        try {
            random = SecureRandom.getInstanceStrong();
        } catch (NoSuchAlgorithmException e) {
            throw new GlobalException(Cause.unknown);
        }
    }

    public static String getRandomString(int length) { // length表示生成字符串的长度

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < base.length(); i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

    public static String getRandomNum(int length) { // length表示生成字符串的长度
        String base = "0123456789";
        SecureRandom random = new SecureRandom();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

    /**
     * 权重随机
     *
     * @param weights 权重数据
     * @return 随机到的下标
     */
    public static int weightedRandom(List<Integer> weights) {
        int random = new SecureRandom().nextInt(weights.stream().reduce(0, Integer::sum));
        int i = 0;
        while (random > weights.get(i)) {
            random -= weights.get(i++);
        }
        return i;
    }

    /**
     * 权重随机
     *
     * @param weights 权重数据
     * @return 随机到的下标
     */
    public static int enumeratedDistributionSample(List<Integer> weights) {
        List<Pair<Integer, Double>> itemWeights = new ArrayList<>();
        for (int i = 0; i < weights.size(); i++) {
            Pair<Integer, Double> pair = new Pair<>(i, weights.get(i).doubleValue());
            itemWeights.add(pair);
        }
        return new EnumeratedDistribution<>(itemWeights).sample();
    }

    /**
     * 权重分配
     *
     * @param weights    权重值
     * @param currentSum 历史分配订单数量
     * @return 下标
     */
    public static int weightAssign(List<Integer> weights, Integer currentSum) {
        //取最大公约数
        Integer maxDivisor = getMaxDivisor(weights);

        // 获取权重和
        Integer weightSum = weights.stream().mapToInt(p -> p.intValue()).sum();
        // 获取洗牌后的位置
        Integer current = shuffle(currentSum, weightSum / maxDivisor, 3, 10);
        // 获取对应的通道下标
        int i = 0;
        while (current > weights.get(i) / maxDivisor) {
            current -= weights.get(i++) / maxDivisor;
        }
        return i;
    }

    /**
     * 定向偏移+
     *
     * @param current 历史分配订单数量
     * @param total   权重总和
     * @param cutNum  偏移量
     * @param foldNum 折叠次数
     * @return 下标
     */
    public static int shuffle(int current, int total, int cutNum, int foldNum) {
        int half = (int) Math.ceil(total / 2D);
        for (int i = 0; i < foldNum; i++) {
            current = (current + cutNum) % total + 1;
            if (half >= current) {
                current = 2 * current - 1;
            } else {
                current = 2 * (current - half);
            }
        }
        return current;
    }

    /**
     * 获取最大公约数
     *
     * @param numList
     * @return
     */
    public static int getMaxDivisor(List<Integer> numList) {
        int temp = 0;
        int max = 0;
        int min = 0;
        for (int i = 0; i < numList.size() - 1; i++) {
            if (max == 0)
                max = numList.get(i);
            min = numList.get(i + 1);
            while (min != 0) {
                temp = max % min;
                max = min;
                min = temp;
            }
        }
        if (max == 0) {
            max = 1;
        }
        return max;
    }

}
